msg_tool\scripts\circus/
script.rs

1//! Circus Script File (.mes)
2use super::info::*;
3use crate::ext::io::*;
4use crate::scripts::base::*;
5use crate::types::*;
6use crate::utils::encoding::{decode_to_string, encode_string};
7use anyhow::Result;
8
9#[derive(Debug)]
10/// Circus MES Script Builder
11pub struct CircusMesScriptBuilder {}
12
13impl CircusMesScriptBuilder {
14    /// Creates a new instance of `CircusMesScriptBuilder`.
15    pub const fn new() -> Self {
16        CircusMesScriptBuilder {}
17    }
18}
19
20impl ScriptBuilder for CircusMesScriptBuilder {
21    fn default_encoding(&self) -> Encoding {
22        Encoding::Cp932
23    }
24
25    fn build_script(
26        &self,
27        buf: Vec<u8>,
28        _filename: &str,
29        encoding: Encoding,
30        _archive_encoding: Encoding,
31        config: &ExtraConfig,
32        _archive: Option<&Box<dyn Script>>,
33    ) -> Result<Box<dyn Script>> {
34        Ok(Box::new(CircusMesScript::new(buf, encoding, config)?))
35    }
36
37    fn extensions(&self) -> &'static [&'static str] {
38        &["mes"]
39    }
40
41    fn script_type(&self) -> &'static ScriptType {
42        &ScriptType::Circus
43    }
44
45    fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
46        try_parse_header(MemReaderRef::new(&buf[..buf_len])).ok()
47    }
48}
49
50fn try_parse_header(mut data: MemReaderRef<'_>) -> Result<u8> {
51    let head0 = data.read_i32()?;
52    let head1 = data.read_i32()?;
53    if head1 == 0x3 {
54        let offset = head0 as u64 * 0x6 + 0x4;
55        let version = data.peek_u16_at(offset)?;
56        if ScriptInfo::query_by_version(version).is_some() {
57            return Ok(10);
58        }
59    } else {
60        let offset = head0 as u64 * 0x4 + 0x4;
61        let version = data.peek_u16_at(offset)?;
62        if ScriptInfo::query_by_version(version).is_some() {
63            return Ok(10);
64        }
65    }
66    Err(anyhow::anyhow!("Not a Circus MES script"))
67}
68
69#[derive(Debug)]
70struct Token {
71    offset: usize,
72    length: usize,
73    value: u8,
74}
75
76/// Circus MES Script
77pub struct CircusMesScript {
78    data: Vec<u8>,
79    encoding: Encoding,
80    is_new_ver: bool,
81    version: u16,
82    info: &'static ScriptInfo,
83    asm_bin_offset: usize,
84    blocks_offset: usize,
85    tokens: Vec<Token>,
86}
87
88impl CircusMesScript {
89    /// Creates a new `CircusMesScript` from the given data and configuration.
90    ///
91    /// * `data` - The data to read the MES script from.
92    /// * `encoding` - The encoding to use for string fields.
93    /// * `config` - Extra configuration options.
94    pub fn new(data: Vec<u8>, encoding: Encoding, config: &ExtraConfig) -> Result<Self> {
95        let head0 = i32::from_le_bytes(data[0..4].try_into()?);
96        let head1 = i32::from_le_bytes(data[4..8].try_into()?);
97        let mut is_new_ver = false;
98        let mut version = 0;
99        let mut info = config
100            .circus_mes_type
101            .as_ref()
102            .and_then(|name| ScriptInfo::query(name.as_ref()));
103        let mut asm_bin_offset = 0;
104        let mut blocks_offset = 0;
105        if head1 == 0x3 {
106            let offset = head0 * 0x6 + 0x4;
107            if data.len() > offset as usize {
108                if data.len() > offset as usize + 3 {
109                    version =
110                        u16::from_le_bytes(data[offset as usize..offset as usize + 2].try_into()?);
111                    if info.is_none() {
112                        info = ScriptInfo::query_by_version(version);
113                    }
114                    asm_bin_offset = offset as usize + 3;
115                }
116                blocks_offset = 8;
117            }
118            is_new_ver = true;
119        } else {
120            let offset = head0 * 0x4 + 0x4;
121            if data.len() > offset as usize {
122                if data.len() > offset as usize + 2 {
123                    version =
124                        u16::from_le_bytes(data[offset as usize..offset as usize + 2].try_into()?);
125                    if info.is_none() {
126                        info = ScriptInfo::query_by_version(version);
127                    }
128                    asm_bin_offset = offset as usize + 2;
129                }
130                blocks_offset = 4;
131            }
132        }
133        let info = info.ok_or(anyhow::anyhow!("Failed to detect version."))?;
134        let mut tokens = Vec::new();
135        let mut offset = 0;
136        let asm_bin_size = if asm_bin_offset == 0 {
137            0
138        } else {
139            data.len() - asm_bin_offset
140        };
141        while offset < asm_bin_size {
142            let value = data[asm_bin_offset + offset];
143            let length = if info.uint8x2.its(value) {
144                0x03
145            } else if info.uint8str.its(value) {
146                let mut len = 0x3;
147                let mut temp = data[asm_bin_offset + offset + len - 1];
148                while temp != 0x00 {
149                    len += 0x1;
150                    if asm_bin_offset + offset + len >= data.len() {
151                        break;
152                    }
153                    temp = data[asm_bin_offset + offset + len - 1];
154                }
155                len
156            } else if info.string.its(value) || info.encstr.its(value) {
157                let mut len = 1;
158                let mut temp = data[asm_bin_offset + offset + len - 1];
159                while temp != 0x00 {
160                    len += 0x1;
161                    if asm_bin_offset + offset + len >= data.len() {
162                        break;
163                    }
164                    temp = data[asm_bin_offset + offset + len - 1];
165                }
166                len
167            } else if info.uint16x4.its(value) {
168                0x09
169            } else {
170                return Err(anyhow::anyhow!(format!(
171                    "Unknown token type: 0x{:02X} at offset {}",
172                    value,
173                    asm_bin_offset + offset
174                )));
175            };
176            let token = Token {
177                offset,
178                length,
179                value,
180            };
181            offset += length;
182            tokens.push(token);
183        }
184        Ok(CircusMesScript {
185            data,
186            encoding,
187            is_new_ver,
188            version,
189            info,
190            asm_bin_offset,
191            blocks_offset,
192            tokens,
193        })
194    }
195}
196
197impl std::fmt::Debug for CircusMesScript {
198    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199        f.debug_struct("CircusMesScript")
200            .field("encoding", &self.encoding)
201            .field("is_new_ver", &self.is_new_ver)
202            .field("version", &self.version)
203            .field("info", &self.info)
204            .field("asm_bin_offset", &self.asm_bin_offset)
205            .field("blocks_offset", &self.blocks_offset)
206            .field("tokens", &self.tokens)
207            .finish_non_exhaustive()
208    }
209}
210
211impl Script for CircusMesScript {
212    fn default_output_script_type(&self) -> OutputScriptType {
213        OutputScriptType::Json
214    }
215
216    fn default_format_type(&self) -> FormatOptions {
217        FormatOptions::Fixed {
218            length: 32,
219            keep_original: false,
220            break_words: false,
221            insert_fullwidth_space_at_line_start: true,
222            break_with_sentence: true,
223            #[cfg(feature = "jieba")]
224            break_chinese_words: true,
225            #[cfg(feature = "jieba")]
226            jieba_dict: None,
227        }
228    }
229
230    fn extract_messages(&self) -> Result<Vec<Message>> {
231        let mut mes = vec![];
232        let mut name = None;
233        for token in self.tokens.iter() {
234            let mut t = None;
235            if self.info.encstr.its(token.value) {
236                let mut text = self.data[self.asm_bin_offset + token.offset + 1
237                    ..self.asm_bin_offset + token.offset + token.length - 1]
238                    .to_vec();
239                for t in text.iter_mut() {
240                    *t = (*t).overflowing_add(self.info.deckey).0;
241                }
242                t = Some(decode_to_string(self.encoding, &text, true)?);
243                // println!("Token(enc): {:?}, {}", token, t.as_ref().unwrap());
244            } else if token.value == self.info.optunenc {
245                let text = &self.data[self.asm_bin_offset + token.offset + 1
246                    ..self.asm_bin_offset + token.offset + token.length - 1];
247                t = Some(decode_to_string(self.encoding, text, true)?);
248                // println!("Token: {:?}, {}", token, t.as_ref().unwrap());
249            }
250            match t {
251                Some(t) => {
252                    if token.value == self.info.nameopcode {
253                        name = Some(t);
254                    } else {
255                        let message = Message::new(t, name.take());
256                        mes.push(message);
257                    }
258                }
259                None => {}
260            }
261        }
262        Ok(mes)
263    }
264
265    fn import_messages<'a>(
266        &'a self,
267        messages: Vec<Message>,
268        writer: Box<dyn WriteSeek + 'a>,
269        _filename: &str,
270        encoding: Encoding,
271        replacement: Option<&'a ReplacementTable>,
272    ) -> Result<()> {
273        let mut repls = Vec::new();
274        if !encoding.is_jis() {
275            fn insert_repl(
276                repls: &mut Vec<(String, String)>,
277                s: &'static str,
278                encoding: Encoding,
279            ) -> Result<()> {
280                let jis = encode_string(Encoding::Cp932, s, true)?;
281                let out = decode_to_string(encoding, &jis, true)?;
282                repls.push((s.to_string(), out));
283                Ok(())
284            }
285            let _ = insert_repl(&mut repls, "{", encoding);
286            let _ = insert_repl(&mut repls, "/", encoding);
287            let _ = insert_repl(&mut repls, "}", encoding);
288            if repls.len() < 3 {
289                eprintln!(
290                    "Warning: Some replacements cannot used in current encoding. Ruby text may be broken."
291                );
292                crate::COUNTER.inc_warning();
293            }
294        }
295        if let Some(repl) = replacement {
296            for (k, v) in repl.map.iter() {
297                repls.push((k.to_string(), v.to_string()));
298            }
299        }
300
301        let source = MemReaderRef::new(&self.data);
302        let mut patcher = BinaryPatcher::new(source, writer, |pos| Ok(pos), |pos| Ok(pos))?;
303
304        let mut pending_messages: Vec<Message> = messages.into_iter().rev().collect();
305        let mut current_message = pending_messages.pop();
306        let mut block_updates: Vec<(u64, u32)> = Vec::new();
307        let mut block_index = 0usize;
308
309        for token in &self.tokens {
310            let token_start = (self.asm_bin_offset + token.offset) as u64;
311            patcher.copy_up_to(token_start)?;
312
313            if !self.is_new_ver {
314                let block_offset = (self.blocks_offset + block_index * 4) as u64;
315                let new_offset = patcher.map_offset(token_start)?;
316                let offset_value = (new_offset - self.asm_bin_offset as u64 + 2) as u32;
317                block_updates.push((block_offset, offset_value));
318                block_index += 1;
319            }
320
321            if self.info.is_message_opcode(token.value) {
322                if current_message.is_none() {
323                    current_message = pending_messages.pop();
324                    if current_message.is_none() {
325                        return Err(anyhow::anyhow!("No more messages to import"));
326                    }
327                }
328
329                let mut text = {
330                    let message = current_message.as_mut().unwrap();
331                    if self.info.is_name_opcode(token.value) {
332                        match message.name.take() {
333                            Some(name) => name,
334                            None => {
335                                let msg = message.message.clone();
336                                current_message = None;
337                                msg
338                            }
339                        }
340                    } else {
341                        let msg = message.message.clone();
342                        current_message = None;
343                        msg
344                    }
345                };
346
347                for (from, to) in &repls {
348                    text = text.replace(from, to);
349                }
350
351                let mut token_bytes = Vec::with_capacity(text.len() + 2);
352                token_bytes.push(token.value);
353                let mut encoded = encode_string(encoding, &text, false)?;
354                if self.info.is_encrypted_message(token.value) {
355                    if encoded.contains(&self.info.deckey) {
356                        eprintln!(
357                            "Warning: text contains deckey 0x{:02X}, text may be truncated: {}",
358                            self.info.deckey, text,
359                        );
360                        crate::COUNTER.inc_warning();
361                    }
362                    for b in &mut encoded {
363                        *b = (*b).overflowing_sub(self.info.deckey).0;
364                    }
365                }
366                token_bytes.extend_from_slice(&encoded);
367                token_bytes.push(0x00);
368                patcher.replace_bytes(token.length as u64, &token_bytes)?;
369                continue;
370            }
371
372            if self.is_new_ver && (token.value == 0x03 || token.value == 0x04) {
373                let block_offset = (self.blocks_offset + block_index * 4) as u64;
374                let original_block = patcher.input.cpeek_u32_at(block_offset)?;
375                let new_offset = patcher.map_offset(token_start)?;
376                let offset = (new_offset - self.asm_bin_offset as u64 + token.length as u64) as u32;
377                let value = (original_block & (0xFF << 0x18)) | offset;
378                block_updates.push((block_offset, value));
379                block_index += 1;
380            }
381
382            let token_end = token_start + token.length as u64;
383            patcher.copy_up_to(token_end)?;
384        }
385
386        patcher.copy_up_to(self.data.len() as u64)?;
387
388        for (offset, value) in block_updates {
389            patcher.patch_u32(offset, value)?;
390        }
391
392        Ok(())
393    }
394}